package com.smartalarmclock.Model;

import java.util.TreeSet;

import com.smartalarmclock.Model.Exceptions.DataEntityDoesNotExistException;
import com.smartalarmclock.Model.Exceptions.InvalidDeleteDataEntityOperationException;
import com.smartalarmclock.Model.Exceptions.InvalidNullReferenceOnDeleteException;
import com.smartalarmclock.Model.Exceptions.InvalidReferenceToRemoveException;
import com.smartalarmclock.Model.Exceptions.InvalidSaveDataEntityOperationException;

import android.content.Context;

abstract class ReferencedDataEntity extends DataEntity implements IReferencedDataEntity {

	// Members
	
	protected TreeSet<IReferencingDataEntity> m_ReferencingEntities;
	
	// Constructors
	
	/**
	 * Constructor for a new instance.
	 */
	protected ReferencedDataEntity() {
		super();
		m_ReferencingEntities = new TreeSet<IReferencingDataEntity>();
	}
	
	/**
	 * Constructor for an instance loaded from the DB
	 * @param id
	 */
	protected ReferencedDataEntity(int id) {
		super(id);
		m_ReferencingEntities = new TreeSet<IReferencingDataEntity>();
	}
	
	// IReferencedDataEntity implementation
	
	/**
	 * Registers the data entities referencing it.
	 * @param referencingEntity the referencing entity to be registered.
	 */
	@Override
	public void RegisterReference(IReferencingDataEntity referencingEntity) {
		m_ReferencingEntities.add(referencingEntity);
	}
	
	/**
	 * Removes the registry of the data entity.
	 * @param referencingEntity the referencing entity to be unregistered.
	 */
	@Override
	public void UnregisterReference(IReferencingDataEntity referencingEntity) {
		m_ReferencingEntities.remove(referencingEntity);
	}
	
	@Override
	public void Delete(Context context)	throws InvalidDeleteDataEntityOperationException, InvalidSaveDataEntityOperationException {		
		// Removing references to this entity
		RemoveReferencesFromReferencingEntities(context);
		
		super.Delete(context);
	}
		
	// Private Methods
	
	/**
	 * Removes the references to itself from other referencing entities 
	 * @param context the context of the request (the originating Activity)
	 * @throws InvalidNullReferenceOnDeleteException if the reference cannot be null and the removal is invalid.
	 * @throws InvalidReferenceToRemoveException if it is not referenced at all.
	 */
	private void RemoveReferencesFromReferencingEntities(Context context) throws InvalidNullReferenceOnDeleteException, InvalidReferenceToRemoveException, InvalidSaveDataEntityOperationException {
		LoadReferencingEntities(context);
		
		// Validating deletion from all referencing entities before attempting to delete any. This way nothing will be
		// updated in the DB before we are sure the operation will be completed correctly.
		for (IReferencingDataEntity entity : m_ReferencingEntities) {
			if (!entity.IsReferenced(context, this)) {
				throw new InvalidReferenceToRemoveException(entity.getClass(), entity.GetId(), getClass(), GetId());
			}
			
			if (!entity.CanRemoveReference(context, this)) {
				throw new InvalidNullReferenceOnDeleteException(entity.getClass(), entity.GetId(), getClass(), GetId());
			}
		}
		
		// All references can be removed so removing all references.
		// We are not iterating through the references since the removal of the reference removes
		// the referencing entity from the list, which cannot be done while iterating.
		IReferencingDataEntity referencingEntity = null;
		while (!m_ReferencingEntities.isEmpty()) {
			referencingEntity = m_ReferencingEntities.iterator().next();
			referencingEntity.RemoveReference(context, this);
			referencingEntity.Save(context);
		}
	}
	
	/**
	 * Loads all referencing entities from the DB using the reference mapping.
	 * @param context the context of the request
	 */
	private void LoadReferencingEntities(Context context) {
		try {
			m_ReferencingEntities.addAll(SmartAlarmClockDB.GetInstance().LoadReferencingEntities(context, this));
		}
		catch (DataEntityDoesNotExistException e) {
			// Do nothing
		}
	}
}
